﻿using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;
using Yz.Base;

namespace Yz.Mvc.Filters
{
    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
    public class RoleAuthorizeAttribute : Attribute, IAuthorizationFilter
    {
        private static readonly char[] _splitParameter = URoleName.Separator.ToCharArray();
        private string _roles;
        private string[] _rolesSplit = new string[0];
        private string _users;
        private string[] _usersSplit = new string[0];
        public string Roles
        {
            get => _roles ?? string.Empty;
            set
            {
                _roles = value;
                _rolesSplit = SplitString(value);
            }
        }
        public string Users
        {
            get => _users ?? string.Empty;
            set
            {
                _users = value;
                _usersSplit = SplitString(value);
            }
        }
        public RoleAuthType AuthType { get; set; } = RoleAuthType.Or;
        public RoleType RoleType { get; set; } = RoleType.Multiple;
        public virtual void OnAuthorization(AuthorizationFilterContext filterContext)
        {
            if (filterContext == null)
            {
                throw new ArgumentNullException("httpContext");
            }

            if (filterContext.ActionDescriptor is ControllerActionDescriptor descriptor && descriptor != null && descriptor.MethodInfo.GetCustomAttributes(true).Any(p => p is AllowAnonymousAttribute))
            {
                return;
            }
            if (!AuthorizeCore(filterContext.HttpContext))
            {
                filterContext.Result = new RedirectResult("/Home/Index");
            }
        }

        internal static string[] SplitString(string original)
        {
            if (string.IsNullOrEmpty(original))
            {
                return new string[0];
            }
            IEnumerable<string> source =
                from piece in original.Split(_splitParameter)
                let trimmed = piece.Trim()
                where !string.IsNullOrEmpty(trimmed)
                select trimmed;
            return source.ToArray<string>();
        }
        /// <summary>处理未能授权的 HTTP 请求。</summary>
        /// <param name="filterContext">封装有关使用 <see cref="T:System.Web.Mvc.AuthorizeAttribute" /> 的信息。<paramref name="filterContext" /> 对象包括控制器、HTTP 上下文、请求上下文、操作结果和路由数据。</param>
        protected virtual void HandleUnauthorizedRequest(AuthorizationFilterContext filterContext)
        {
            filterContext.Result = new UnauthorizedResult();
        }

        /// <summary>重写时，提供一个入口点用于进行自定义授权检查。</summary>
        /// <returns>如果用户已经过授权，则为 true；否则为 false。</returns>
        /// <param name="httpContext">HTTP 上下文，它封装有关单个 HTTP 请求的所有 HTTP 特定的信息。</param>
        /// <exception cref="T:System.ArgumentNullException">
        /// <paramref name="httpContext" /> 参数为 null。</exception>
        protected virtual bool AuthorizeCore(HttpContext httpContext)
        {
            if (httpContext == null)
                throw new ArgumentNullException(nameof(httpContext));
            IPrincipal user = httpContext.User;
            bool authOk = user.Identity.IsAuthenticated;
            bool userOk = (_usersSplit.Length <= 0 || _usersSplit.Contains(user.Identity.Name, StringComparer.OrdinalIgnoreCase));
            bool roleOk = false;
            if (_rolesSplit.Length <= 0)
            {
                roleOk = true;
            }
            else
            {
                ArrayList arr = new ArrayList(_rolesSplit);
                _rolesSplit = (string[])arr.ToArray(typeof(string));
                if (AuthType == RoleAuthType.And)
                {
                    roleOk = _rolesSplit.All(new Func<string, bool>(user.IsInRole));
                }
                else
                {
                    roleOk = _rolesSplit.Any(new Func<string, bool>(user.IsInRole));
                }
            }
            return authOk && userOk && roleOk;
        }

    }
}